package com.qf.user.manager.filter;

import com.qf.common.model.QfUser;
import com.qf.common.utils.JsonUtil;
import com.qf.common.utils.JwtUtil;
import com.qf.user.manager.bean.LoginUser;
import com.qf.user.manager.service.UserService;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

/**
 * 认证过滤器
 * 对请求头中的token令牌也就是jwt进行解析
 * 将解析到的用户名到数据库中从新查询
 * 将查询到的authtication对象放入SpringSecurityContextHolder全局对象中
 *
 * @author 千锋健哥
 */
@Component
public class AuthFilter extends OncePerRequestFilter {

    @Autowired
    private UserService userService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        //1. 获取用户访问的uri地址
        String path = request.getRequestURI();
        //2. 判断如果是登录路径, 放行
        if (path.contains("/user/auth")) {
            chain.doFilter(request, response);
            return;
        }

        //3. 获取请求头中的jwt令牌
        String token = request.getHeader("token");

        //4. 判断如果jwt令牌不为空, 那么进行解析

        if (!StringUtils.isEmpty(token)) {
            String userName = null;
            try {
                //5. 解析jwt令牌
                Claims claims = JwtUtil.parseJWT(token);
                userName = claims.getSubject();
            } catch (Exception e) {
                //6. 解析失败, 返回给前端错误信息
                WriteJSON(request, response, new AccessDeniedException("非法token令牌, 请从新登录, 无权限访问!"));
                return;
            }

            //7. 如果解析成功, 那么根据用户名获取当前用户对象
            QfUser user = userService.getUserByUserName(userName);
            //8. 根据用户名, 从新获取用户对应的菜单集合
            List<String> menuList = userService.findUserMenuByUserName(userName);
            //9. 将用户菜单对象封装成, SpringSecurity的权限集合对象
            List<SimpleGrantedAuthority> authorities = new ArrayList<>();
            for (String menu : menuList) {
                authorities.add(new SimpleGrantedAuthority(menu));
            }

            //10. 将用户对象, 菜单集合对象, 封装成UserDetails对象
            LoginUser loginUser = new LoginUser();
            //从数据库中查询到的用户对象
            loginUser.setQfUser(user);
            //根据用户名从数据库查到的这个用户的菜单集合对象
            loginUser.setAuthorities(authorities);

            //11. 返回给SpringSecurity框架, 在SpringSecurityContextHolder全局对象中保存
            UsernamePasswordAuthenticationToken authenticationToken =
                    new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }

       chain.doFilter(request, response);
    }


    /**
     * json处理
     * @param request
     * @param response
     * @param obj
     * @throws IOException
     * @throws ServletException
     */
    private void WriteJSON(HttpServletRequest request,
                           HttpServletResponse response,
                           Object obj) throws IOException, ServletException {
        //这里很重要，否则页面获取不到正常的JSON数据集
        response.setContentType("application/json;charset=UTF-8");

        //跨域设置
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Method", "POST,GET");
        //输出JSON
        PrintWriter out = response.getWriter();
        out.write(JsonUtil.toJsonString(obj));
        out.flush();
        out.close();
    }
}
